var fs=require('fs');
var gd=require('gd');
var Process=require('process').Process;
var exif_reader = require("exif").Reader;

var to_latin_src=[
		/\s+/g,
		/й/g ,/ц/g ,/у/g ,/к/g ,/е/g ,/н/g ,/г/g ,/ш/g ,/щ/g ,/з/g ,/х/g ,/ъ/g ,
		/ф/g ,/ы/g ,/в/g ,/а/g ,/п/g ,/р/g ,/о/g ,/л/g ,/д/g ,/ж/g ,/э/g ,
		/я/g ,/ч/g ,/с/g ,/м/g ,/и/g ,/т/g ,/ь/g ,/б/g ,/ю/g ,
		/Й/g ,/Ц/g ,/У/g ,/К/g ,/Е/g ,/Н/g ,/Г/g ,/Ш/g ,/Щ/g ,/З/g ,/Х/g ,/Ъ/g ,
		/Ф/g ,/Ы/g ,/В/g ,/А/g ,/П/g ,/Р/g ,/О/g ,/Л/g ,/Д/g ,/Ж/g ,/Э/g ,
		/Я/g ,/Ч/g ,/С/g ,/М/g ,/И/g ,/Т/g ,/Ь/g ,/Б/g ,/Ю/g ,
		/[^\w]+/g
];
var to_latin_dst=[
		"-",
		"j" ,"ts","u" ,"k" ,"e" ,"n" ,"g" ,"sh","sh","z" ,"h" ,""  ,
		"f" ,"i" ,"v" ,"a" ,"p" ,"r" ,"o" ,"l" ,"d" ,"zh","e" ,
		"ja","ch","s" ,"m" ,"i" ,"t" ,""  ,"b" ,"ju",
		"J" ,"TS","U" ,"K" ,"E" ,"N" ,"G" ,"SH","SH","Z" ,"H" ,""  ,
		"F" ,"I" ,"V" ,"A" ,"P" ,"R" ,"O" ,"L" ,"D" ,"ZH","E" ,
		"JA","CH","S" ,"M" ,"I" ,"T" ,""  ,"B" ,"JU",
		"-"
];
/*****************************************************************************/
exports.functions={
	/*
		Function: functions.uploads_folder

		Returns:
			path to uploads
	*/
	uploads_folder: function(space,diskpath) {
		if (diskpath) {
			return space.site.paths.html+(space.site.paths.uploads_subfolder||space.system.paths.uploads_subfolder);
		} else {
			return "/"+(space.site.paths.uploads_subfolder||space.system.paths.uploads_subfolder);
		}
	},

	/*
		Function: functions.parse_grids

		Parses ajajform grids in space.fields and returns them as an object

		Returns:
			Hash of grids, each grid is hash of hashes
	*/
	parse_grids: function(space) {
		var hashes={};
		for (var p in space.fields) {
			var arr;
			if (!(arr=p.match(/^(\w+)-([\w\.]+)-(\w+)$/))) continue;
			if (!hashes[arr[1]]) hashes[arr[1]]={};
			if (!hashes[arr[1]][arr[2]]) hashes[arr[1]][arr[2]]={};
			hashes[arr[1]][arr[2]][arr[3]]=space.fields[p];
		}
		return hashes;
	},	
	/*
		Function: functions.save_grid
		
		Saves parsed grid

		Parameters:
			hashes - A hash of hashes, representing a grid
			sqlpath -a path to sqls, can be a generated models of custom
			rules - hash of:
			rules.existing - function
			rules.insert - function
			rules.update - function
			rules.delold - function

		Return:
			none
	*/
	save_grid: function (space,hashes,sqlpath,rules) {
		var ex={};
		var mod=sqlpath.match(/^_models\/(\w+)/);
		var exlist;
		if (mod) {
			exlist=space.mfact.list(mod[1],rules.existing());
		} else {
			exlist=space.site.sql.execute_and_fetch(sqlpath+"/list",rules.existing());
		}
		exlist.forEach(function(p) {ex[p.id]=1;});
		for (var id in hashes) {
			var hash=hashes[id];
			if (id.match(/new/)) {
				rules.insert(hash,id);
				space.site.sql.execute(sqlpath+"/insert",hash);
			} else {
				rules.update(hash,id);
				space.site.sql.execute(sqlpath+"/update",hash);
				ex[id]=0;
			}
		}
		for (var id in ex) {
			if (ex[id]==0) continue;
				var hash={};
				rules.delold(hash,id);
				space.site.sql.execute(sqlpath+"/delete",hash);
		}
	},
	/*
		Function: functions.reorder_grids
		Parameters:
			sqlprexix
	*/
	reorder_grids: function(space,sqlprefix) {
		var arr=[];
		var val=[];
		space.fields.ordering.split(/,/).forEach(function(x) {
			arr.push(x.match(/-(\d+)$/)[1]);
		});
		for (var i=0;i!=arr.length;i++) {
			val.push(1*(space.site.sql.execute_and_fetch_one(sqlprefix+"get_ordering",{id:arr[i]}).ordering));
			space.site.sql.execute(sqlprefix+"set_ordering",{id:arr[i],ordering: -i-1});
		}
		val.sort(function(a,b) {return a - b;});
		for (var i=0;i!=arr.length;i++) {
			space.site.sql.execute(sqlprefix+"set_ordering",{id:arr[i],ordering: val[i]});
		}
	},
	/*
		Function: functions.save_checkboxlist
	*/
	save_checkboxlist: function(space,name,prefix,name_id,search_filter,create_filter) {
		var all={};
		space.mfact.list(name,search_filter).forEach(function(x) {all[x[name_id]]=x.id;});
		for (var k in space.fields) {
			if (k.substr(0,prefix.length)!=prefix) continue;
			var id=k.substr(prefix.length);
			if (!all[id]) {
				var h=create_filter;
				h[name_id]=id;
				var item=space.mfact.from_object(name,h);
				item.Save();
			}
			delete all[id];
		}
		for (var id in all) {
			space.mfact.del_one(name,all[id]);
		}
	},
	/*
		Function: functions.file_group_path

		Calculates filesystem path to file_group

		Parameters:
			fg_id - File group id, an ID from Tfile_groups table
		
		Returns:
			Filesystem path to file_group
	*/
	file_group_path: function(space,fg_id) {
		var ret="";
		while (fg_id && fg_id!="0") {
			var grp=space.site.sql.execute_and_fetch_one("file_groups/get",{file_group_id:fg_id});
			ret=(grp.code||grp.id)+"/"+ret;
			fg_id=grp.parent_id;
		}
		return space.functions.uploads_folder(space)+ret;
	},
	init_files: function(space,array,colname,colname2) {
		for (var i=0;i<array.length;i++) space.functions.init_file(space,array[i],colname,colname2);
	},
	/*
		Function: functions.init_file

		Fills object, creating a valid URL to file

		Parameters:
			model - an object
			colname - file prefix in "model" object
			colname2 - (optional, defaults to colname+"_id")
		
		Returns:
			None
			* a colname+"_name" value inside model[] object is filled.
	*/
	init_file: function(space,model,colname,colname2) {
		if (!colname2) colname2=colname+"_id";
		if (!model[colname2]) {
			model[colname+"_name_or_filename"]="";
			model[colname+"_name"]="";
			return;
		}
		model[colname+"_url"]=space.functions.file_group_path(space,model[colname+"_file_group_id"])+model[colname2]+"."+model[colname+"_ext"];
		model[colname+"_name_or_filename"]=model[colname+"_name"]||model[colname+"_orig_filename"];
		if (model[colname+"_name"]==undefined || model[colname+"_name"]==null) model[colname+"_name"]="";
	},

	/*
		Function: functions.guess_mime
			
			Guesses mime type.
	*/
	guess_mime: function(space,ext) {
		if (ext.match(/^jpe?g$/i)) return "image/jpeg";
		if (ext.match(/^png$/i)) return "image/png";
		if (ext.match(/^gif$/i)) return "image/gif";
		return "application/binary";
	},

	/*
		Function: functions.guess_file_type
			
			Guesses file type. Right now works only by extention.

		Parameters:
			ext - File extention
			filedata - File contents

		Returns:
			* 1 for image
			* 2 for video
			* 3 for audio
			* 4 for others
	*/
	guess_file_type: function(space,ext,filedata) {
		if (ext.match(/^(png|gif|jpg|jpeg)$/)) return 1;
		if (ext.match(/^(avi|flv|mpg)$/)) return 2;
		if (ext.match(/^(mp3)$/)) return 3;
		return 4;
	},
	/*
		Function: functions.mkdir_rec

		Makes a directory recursively

		Parameters:
			basepath - base path
			path - a directory or file name to create
			isFile - true/false, is name in "path" variable a filename or a foldername

		Returns:
			none
	*/
	mkdir_rec: function (basepath,path,isFile) {
		var arr=path.split(/\//);
		var max=isFile?arr.length-1:arr.length;
		for (var i=0;i<max;i++) {
			basepath+=arr[i]+"/";
			var d=new fs.Directory(basepath);
			if (!d.isDirectory())
				try {
					d.create();
				} catch(e) {
					throw e+" (path="+basepath+")\n";
				}
		}
	},
	/*
		Function: functions.ajajform_file_group_value
	*/
	ajajform_file_group_value: function (space,file_group_id) {
		var r=space.site.sql.execute_and_fetch_one("file_groups/get",{file_group_id:file_group_id});
		r.files=space.site.sql.execute_and_fetch("files/list",{file_group_id:file_group_id});
		var path=space.functions.file_group_path(space,file_group_id);
		for (var i=0,s=r.files.length;i<s;i++) {
			var file=r.files[i];
			file.url=path+file.id+"."+file.ext;
			file.name_or_filename=file.name||file.orig_filename;
			if (file.name==undefined || file.name==null) file.name="";
		}
		return r;
	},
	/*
		Function: functions.to_latin

		Converts text to latin, used for transliteration
	*/
	to_latin: function(space,src) {
		if (src==undefined || src==null) return src;
		for (var i=0,s=to_latin_src.length;i<s;i++) {
			src=src.replace(to_latin_src[i],to_latin_dst[i]);
		}
		return src;
	},
	/*
		Function: functions.gen_calc_code

		Generates a calculateable unique code based on some name.
		Can be used for things like making a valid URL based on article or news name.

		Parameters:
			model - model object, containing:
			* model.id - (optional) an id of existing entry in database
			* model.ModelName - a name of model
			* model[src_field] - an original name to use
			* model[dst_field] - (optional) a calculated name to store data
			src_field - field name in object
			dst_field - field name in object

		Returns:
			none
			* model[dst_field] is filled with a result
	*/
	gen_calc_code: function(space,model,src_field,dst_field) {
		var code_src=space.site.refs.functions.to_latin(space,model[src_field]);
		if (code_src==undefined || code_src==null) {
			model[dst_field]=code_src;
			return;
		}
		if (code_src.length>240) code_src=code_src.substr(0,240);
		var j=0;
		var flag=true;
		var code=code_src;
		while (flag==true) {
			var h={limit: 1};
			if (model.id) h.id={type: "<>",value: model.id};
			h[dst_field]=code;
			var m=space.site.mfact.list(model.ModelName,h);
//			throw(space.site.functions.Dumper([h,m]));
//			throw([h.limit,h.id.value,h[dst_field],m]);
			if (m.length!=0) {
				j++;
				code=code_src+"-"+j;
			} else {
				flag=false;
			}
		}
		model[dst_field]=code;
	},
	/*
		Function: functions.tagger_save
	*/
	tagger_save: function(space) {
	},
	/*
		Function: functions.gd_image_type_by_ext

		Parameters:
			ext - File extention

		Returns:
			GD-format image type
	*/
	gd_image_type_by_ext: function(space,ext) {
		switch (ext) {
			case 'png':
				return gd.Image.PNG;
			case 'jpg':
			case 'jpeg':
				return gd.Image.JPEG;
			case 'jpeg':
				return gd.Image.JPEG;
			case 'gif':
				return gd.Image.GIF;
			default:
				throw("Unknown file ext='"+ext+"'");
		}
	},
	/*
		Function: functions.rotate_file90

		Rotates a file in Tfiles by 90 degrees, making a new entry in Tfiles and deleting old one

		Parameters:
			file_id - file id
			val - direction (1 or -1)

		Returns:
			None
			* Filesystem is changed
			* Tfiles is changed
	*/
	rotate_file90: function(space,file_id,val) {
		var file=space.site.sql.execute_and_fetch_one("files/get",{file_id:file_id});
		space.site.sql.execute("files/insert",{
			type_id:file.type_id,
			file_group_id:file.file_group_id,
			orig_filename:file.orig_filename,
			ext:file.ext,
			name:file.name||""
		});
		var path=space.functions.file_group_path(space,file.file_group_id);
		var basepath=space.site.paths.html;
		var fid=space.site.sql.last_insert_id("Tfiles");
		space.functions.rotate_image90(space,basepath+path+file.id+"."+file.ext,basepath+path+fid+"."+file.ext,val,file.ext);
		space.functions.file_delete(space,file.id);
		space.functions.file_update_stats(space,fid);
	},
	/*
		Function: functions.rotate_image90

		Rotates image on hard drive (does not do anything to database)

		Parameters:
			srcfile - source file
			dstfile - destination file
			val - direction (-1 or 1)
			ext - file extention
	*/
	rotate_image90: function(space,srcfile,dstfile,val,ext) {
		if (val!=1 && val!=-1) throw("Can not rotate");
		var image_type=space.functions.gd_image_type_by_ext(space,ext);
		var image=new gd.Image(image_type,srcfile);
		var ow=image.sx();
		var oh=image.sy();
		var dstimage=new gd.Image(gd.Image.TRUECOLOR,oh,ow);
		dstimage.copyRotated(image,oh/2,ow/2,0,0,ow,oh,90*val);
		dstimage.save(image_type,dstfile);
	},
	/*
		Function: functions.rotate_file_exif

		Rotates file if it is image and needs rotating

		Paramters:
			file_id - id from Tfiles

		Returns:
			EXIF.Orientation if rotation was done
			* Changes filesystem
			* Does not change database
	*/
	rotate_file_exif: function(space,file_id) {
		var file=space.site.sql.execute_and_fetch_one("files/get",{file_id:file_id});
		var path=space.functions.file_group_path(space,file.file_group_id);
		var basepath=space.site.paths.html;
		var filename=basepath+path+file.id+"."+file.ext;
		var filename2=basepath+path+file.id+"-tmp."+file.ext;
		var image_data=new fs.File(filename).open("r").read();
		var reader = new exif_reader(image_data);
		var tags=reader.getTags();
		if (!tags.Orientation) return;
		var val;
		if (tags.Orientation==8) val=3;
		if (tags.Orientation==3) val=2;
		if (tags.Orientation==6) val=1;
		if (!val) return;
//		var jt="jpegtran -outfile "+filename2+" -rotate "+(val*90)+" -copy all "+filename;
		var jt="jhead -q -autorot "+filename;
		var r=(new Process()).system(jt);
		if (r) throw "Process().system(\"jt\") returned "+r;
		//space.functions.rotate_image90(space,filename,filename2,val,file.ext);

		var pfiles=new fs.Directory(basepath+path).listFiles();
		var re=new RegExp("^"+file_id+"-preview");
		pfiles.forEach(function(pfile) {
			if (pfile.match(re)) (new fs.File(basepath+path+pfile)).remove();
		});
//		(new fs.File(filename2)).move(filename);
		space.functions.file_update_stats(space,file_id);
		return tags.Orientation;
	},
	/*
		Function: functions.scale_image

		Scales image

		Parameters:
			srcfile - source file
			dstfile - destination file
			w - new width
			h - new height
			mo - modification rule, "", "C", or some else
			moval - modification rule add
			exact - TODO not used
			ext - file extention

		Returns:
			none
			* FIlesystem is changed, new file is created
	*/
	scale_image: function(space,srcfile,dstfile,w,h,mo,moval,exact,ext) {
		var image_type=space.functions.gd_image_type_by_ext(space,ext);
		var image=new gd.Image(image_type,srcfile);
		var ow=image.sx();
		var oh=image.sy();
		var dstimage;
		switch (mo) {
			case "":
				if (ow>w || oh>h) {
					var sc=w/ow;
					var nh,nw;
					if (sc<h/oh) {
						nw=w;
						nh=Math.floor(oh*sc);
						if (nh>h) nh=h;
					} else {
						sc=h/oh;
						nw=Math.floor(ow*sc);
						if (nw>w) nw=w;
						nh=h;
					}
					dstimage=new gd.Image(gd.Image.TRUECOLOR,nw,nh);
					dstimage.alphaBlending(false);
					dstimage.saveAlpha(true);
					dstimage.copyResampled(image,0,0,0,0,nw,nh,ow,oh);
				} else {
					dstimage=image;
					dstimage.alphaBlending(false);
					dstimage.saveAlpha(true);
				}
				break;
/*		case "X":
			if ($ow>$w) {
				my $nh=int($oh*$w/$ow);
				$m->Scale(width=>$w,height=>$nh);
			}
			break;
		case "Y":
			if ($oh>$h) {
				my $nw=int($ow*$h/$oh);
				$m->Scale(width=>$nw,height=>$h);
			}
			break;*/
			case "C":
/*			if (!exact && $srcfile=~/^(.*\/)([^\/]+)$/) {
				if (open IN,"$1.$2.focus") {
					my $f=<IN>;
					chomp $f;
					close IN;
					if ($ow/$w>$oh/$h) {
						$moval=  0 if $f==1 || $f==4 || $f==7;
						$moval=100 if $f==3 || $f==6 || $f==9;
					} else {
						$moval=  0 if $f==1 || $f==2 || $f==3;
						$moval=100 if $f==7 || $f==8 || $f==9;
					}
				}
			}*/
/*			if (ow/w==oh/h) {
			} elsif (ow/w>oh/h) {
				// Width is greater then Height
				var cw=Math.floor(oh*w/h);
				var mo=Math.floor(moval*(ow-cw)/100);
				//$m->Crop(geometry=>$cw."x".$oh."+$mo+0");
			} else {
				var ch=Math.floor(ow*h/w);
				var mo=Math.floor(moval*(oh-ch)/100);
				//$m->Crop(geometry=>$ow."x".$ch."+0+$mo");
			}*/
				if (ow>w || oh>h) {
					var ooh,oow;
					dstimage=new gd.Image(gd.Image.TRUECOLOR,w,h);
					if (ow/w > oh/h) {
						// Width is greater then Height
						ooh=oh;
						oow=Math.floor(oh*w/h);
					} else {
						oow=ow;
						ooh=Math.floor(ow*h/w);
					}
					dstimage.alphaBlending(false);
					dstimage.saveAlpha(true);
					dstimage.copyResampled(image,0,0,(ow-oow)/2,(oh-ooh)/2,w,h,oow,ooh);
	/*				$m->Scale(width=>$w,height=>$h);
					if ($m->Get("width")!=$w || $m->Get("height")!=$h) {
						warn "[4] $dstfile Current geomtery is ".$m->Get("width")."x".$m->Get("height").", w/h=$w/$h\n";
						$m->Crop(geometry=>$w."x".$h."+0+0");
						warn "[5] Current geomtery is ".$m->Get("width")."x".$m->Get("height")."\n";
					}*/
				} else {
					dstimage=image;
				}
				break;
			default:
				throw("Unknown mo "+mo);
		}
		if (!dstimage) {
			throw "scale_image(srcfile="+srcfile+",dstfile="+dstfile+",w="+w+",h="+h+",mo="+mo+",moval="+moval+",exact="+exact+",ext="+ext+" -- dstfile is null";
		}
		dstimage.save(image_type,dstfile);
	},
	/*
		Function: functions.file_update_stats

		Updates file stats, that is - width, height, filesize, seconds (if available)

		Parameters:
			file_id - id in Tfiles

		Returns:
			none
	*/
	file_update_stats: function(space,file_id) {
		var href={
			file_id:file_id,
			width: null,
			height: null,
			filesize: null,
			seconds: null
		};
		var file=space.site.sql.execute_and_fetch_one("files/get",href);
		var path=space.site.paths.html+space.functions.file_group_path(space,file.file_group_id);
		var filename=path+file.id+"."+file.ext;
		href.filesize=new fs.File(filename).stat().size;
//		throw space.Dumper([filename,href.filesize]);
		switch (1*file.type_id) {
			case 1: // Image
				var image_type=space.functions.gd_image_type_by_ext(space,file.ext);
				var image=new gd.Image(image_type,filename);
				href.width=image.sx();
				href.height=image.sy();
				break;
			case 2: // Video
				break;
			case 3: // Sound
				break;
			case 4: // Other
				return;
				break;
		}
//		throw space.Dumper(href);
		space.site.sql.execute("files/set_stats",href);
	},
	/*
		Function: functions.all_files_update_stats

		Updates stats of all files

		Parameters:

		Returns:
			none
	*/
	all_files_update_stats: function(space) {
		space.site.sql.execute_and_fetch_single("select id from Tfiles").forEach(function(x) {
			space.functions.file_update_stats(space,x.id);
		});
	},
	/*
		Function: functions.file_delete

		Deletes a file from a filesystem and from a Tfiles table in database

		Parameters:
			file_id - id in Tfiles

		Returns:
			none
	*/
	file_delete: function(space,file_id) {
		var href={file_id:file_id};
		var file=space.site.sql.execute_and_fetch_one("files/get",href);
		var path=space.site.paths.html+space.functions.file_group_path(space,file.file_group_id);
		var pfiles=new fs.Directory(path).listFiles();
		var re=new RegExp("^"+file_id+"(\\.|-preview)");
		pfiles.forEach(function(pfile) {
			if (pfile.match(re)) (new fs.File(path+pfile)).remove();
		});
		space.site.sql.execute("files/delete",href);
	},
	/*
		Function: functions.recurse_object

		TODO dont know if it is used
	*/
	recurse_object: function(space,object,callback) {
		if (typeof(object)!="object") return;
		if (object.constructor==Array) {
			for (var i=0;i!=object.length;i++) {
				var old=object[i];
				object[i]=callback(space,"array",i,old);
				if (object[i]!==old) continue;
				if (typeof(object[i])=="object") space.functions.recurse_object(space,object[i],callback);			
			}
			return;
		}
		for (var k in object) {
			var old=object[k];
			object[k]=callback(space,"object",k,old);
			if (object[k]!==old) continue;
			if (typeof(object[k])=="object") space.functions.recurse_object(space,object[k],callback);
		}
	},
	/*
		Function: functions.files_load

		Loads files from certain file_group and builds their urls

		Parameters:
			file_group_id - an ID in Tfile_groups table

		Returns:
			Array of file objects
	*/
	files_load: function(space,file_group_id) {
		var r=space.site.sql.execute_and_fetch("files/list",{file_group_id:file_group_id});
		var fgpath=space.functions.file_group_path(space,file_group_id);
		r.forEach(function(x) {
			x.url=fgpath+x.id+"."+x.ext;
		});
		return r;
	},
	/*
		Function: functions.make_file_group_path

		Parameters:
			fgpath - an array of folder names, such as ["my images","summer 2012","visit to grandma"]
			uid - user to create folders
		Returns:
			array of fg_id
	*/
	make_file_group_path: function(space,fgpath,uid) {
		var ret=[];
		var parent_id=0;
		var basepath=space.site.paths.html;
		for (var i=0;i<fgpath.length;i++) {
			var fg=space.site.sql.execute_and_fetch_one("file_groups/get_by_name_and_parent",{name:fgpath[i],parent_id:parent_id});
			if (!fg) {
				space.site.sql.execute("file_groups/insert",{parent_id:parent_id,name:fgpath[i],user_cr_id:uid||space.uid});
				fg=space.site.sql.execute_and_fetch_one("file_groups/get_by_name_and_parent",{name:fgpath[i],parent_id:parent_id});
				var path=space.functions.file_group_path(space,fg.id);
				space.functions.mkdir_rec(basepath,path);
			}
			ret.push(fg.id);
			parent_id=fg.id;
		}
//		throw space.Dumper({fgpath: fgpath, path:path, files:space.req.files, ret:ret});
		return ret;
	},
	/*
		Function: functions.user_upload_file

		Parameters:
			options - a hash of:
			options.inputname - An <input type="file"> name
			options.fg_id - An ID from Tfile_groups table
			options.fg_code - (OR)
			options.fg_name - (OR)
			options.uid - (optional, defaults to space.uid) an ID from Tusers table
			options.filter_func - function
			options.downscale_policy - a policy to downscale (too long, will describe later TODO)

		Returns:
			New file ID, from Tfiles table
	*/
	user_upload_file: function(space,options) {

		var fg_id=space.functions.get_file_group_id(space,options,"fg");
		if (!fg_id) throw "No file group found!";
		if (!options.inputname) throw "No options.inputname";
		var uid=options.uid||space.uid;
		var downscale_policy=options.downscale_policy;

		var fileinfo=space.req.files[options.inputname];
		
		var path=space.functions.file_group_path(space,fg_id);
		var basepath=space.site.paths.html;
		space.functions.mkdir_rec(basepath,path);
		
		if (!fileinfo) return undefined;
		if (!fileinfo.originalName) return undefined;
		var arr1=fileinfo.originalName.match(/\.([a-zA-Z0-9_\-]{1,8})$/);
		if (!arr1) throw "No extention of file "+space.Dumper(fileinfo);
		var ext=arr1[1].toLowerCase();
		var filename=fileinfo.originalName.toLowerCase();
		filename=filename.replace(/^.*[\/\\\|]/);
		filename=filename.replace(/\.([a-zA-Z0-9_\-]{1,8})$/,"");
		var filedata=fileinfo.data;
		var type_id=space.functions.guess_file_type(space,ext,filedata);
		if (options.filter_func) {
			if (!options.filter_func(space,{uid:uid,original:fileinfo,type_id:type_id})) return undefined;
		} else {
			if (type_id==1) {
				if (!downscale_policy) {
					downscale_policy=space.functions.user_get_autoscale_policy(space,"max");
					if (space.fields.downscale) {
						if (downscale_policy && downscale_policy.x) {
							var max_x=1*space.fields.max_x;
							var max_y=1*space.fields.max_y;
							if (max_x<downscale_policy.x && max_x>0) downscale_policy.x=max_x;
							if (max_y<downscale_policy.y && max_y>0) downscale_policy.y=max_y;
							throw space.Dumper([downscale_policy,space.fields]);
						} else {
							downscale_policy={x:space.fields.max_x,y:space.fields.max_y};
						}
					}
				}
			} else if (!space.roles.files) {
				throw "Not image";
			}
		}
//		throw space.Dumper(downscale_policy);
		space.site.sql.execute("files/insert",{
			type_id:type_id,
			file_group_id:fg_id,
			orig_filename:filename,
			ext:ext,
			user_cr_id: uid,
			name:space.fields.name||""
		});
		var fid=space.site.sql.last_insert_id("Tfiles");
		try {
			var srcfile=basepath+path+fid+"."+ext;
			if (fileinfo.existingfile) {
				var f=new fs.File(fileinfo.existingfile);
				f.move(srcfile);
			} else {
				var f=new fs.File(srcfile);
				f.open("w");
				f.write(filedata);
				f.close();
			}
			if (downscale_policy && downscale_policy.x!=-1 && type_id==1) {
				var image_type=space.functions.gd_image_type_by_ext(space,ext);
				var image=new gd.Image(image_type,srcfile);
				var ow=image.sx();
				var oh=image.sy();
				if (ow>downscale_policy.x || oh>downscale_policy.y) {
					var tmpfile=basepath+path+fid+"-notscaled."+ext;
					f.move(tmpfile);
					space.functions.scale_image(space,tmpfile,srcfile,downscale_policy.x,downscale_policy.y,"","","",ext);
					f.remove();
				}
			}
		} catch(e) {
			throw "Can not save file - "+e+", filename: "+basepath+path+fid+"."+ext;
		}
		space.functions.file_update_stats(space,fid);
		return fid;
	},
	/*
		Function: functions.mksid
		
		Generates a unique SID

		Returns:
			A string length of 48 symbols
	*/
	mksid: function() {
		var sid_items="1234567890qwertyuiopasdfghjklzxcvbnm";
		var r="";
		for (var i=0;i<48;i++) {
			r+=sid_items.substr(Math.floor(Math.random()*sid_items.length),1);
		}
		return r;
	},
	/*
		Function: functions.get_option
		
		Paramaters:
			params - an array of names through which to follow to result option value
		Returns:
			A value
	*/
	get_option: function(space,params) {
		var arr=["site","system"];
		for (var j=0;j<arr.length;j++) {
			var a=space[arr[j]];
			for (var i=0;i<params.length;i++) {
				if (!(params[i] in a)) break;
				a=a[params[i]];
				if (i==params.length-1) return a;
			}
		}
		return undefined;
	},

	/*
		Function: functions.date_format

		Parameters:
			d - Date object
		Returns:
			date string, formatted to DD.MM.YYYY
	*/
	date_format: function(space,d) {
		var dd=d.getDate();
		var mm=d.getMonth()+1;
		var yyyy=d.getFullYear();
		return ""+(dd<10?"0":"")+dd+"."+(mm<10?"0":"")+mm+"."+yyyy;
	},

	/*
		Function: functions.date_add

		Parameters:
			d - Date object
			value - a value to add
			type - type of value, "day", "month" or "year"
		Returns:
			Date object
	*/
	date_add: function (space,d,value,type) {
		var r=new Date(d.getTime());
		switch (type) {
			case "day":
				r.setDate(r.getDate()+value);
				break;
			case "month":
				r.setMonth(r.getMonth()+value);
				break;
			case "year":
				r.setFullYear(r.getFullYear()+value);
				break;
		}
		return r;
	},

	date_new: function(space,text) {
		var arr=text.match(/^(\d\d)\.(\d\d)\.(\d\d\d\d)$/);
		if (!arr) return undefined;
		return new Date(arr[3],arr[2]-1,arr[1]);
	},
	/*
		Function: functions.import_files
		Parameters:
		Returns:
	*/
	import_files: function(space,path,user,folder) {
		var folder_id;
		if (folder.match(/^\d+$/)) {
			folder_id=folder;
		} else {
			folder_id=space.site.sql.execute_and_fetch_one("file_groups/get_by_code",{code:folder});
			if (folder_id) {
				folder_id=folder_id.id;
			} else {
				folder_id=space.site.sql.execute_and_fetch_one("file_groups/get_by_name",{name:folder}).id;
			}
		}
		var user_id;
		if (user.match(/^\d+$/)) {
			user_id=user;
		} else {
			user_id=space.site.sql.execute_and_fetch_one("users/get_by_login",{login:user}).id;
		}
		var d=new fs.Directory(path);
		if (!d.exists()) throw("No path!");
		space.fields={};
		space.roles={};
		space.roles.files=1;
		space.req={};
		space.req.files={};
		d.listFiles().sort().forEach(function(filename) {
			if (filename.match(/^\./)) return;
			var options={
				fg_id:folder_id,
				uid:user_id,
				inputname: "aaa",
				downscale_policy: {
					x: -1,
					y: -1
				}
			};
			space.req.files.aaa={
				originalName: filename,
				existingfile: path+"/"+filename
			};
			space.functions.user_upload_file(space,options);
		});
		return "";
	},

	/**
	 *
	 */
	guess_icon: function(space,ext) {
		var basepath=space.site.paths.html;
		var icon="/shared/icons/filebrowser/";
		var testfile=new fs.File(basepath+"/shared/icons/filebrowser/"+ext+".png");
		icon+=(testfile.isFile())?ext:"default";
		icon+=".png";
		return icon;
	},
	
	/**
	 *
	 */
	send_mail: function(space,code,hash) {
		var mail=space.site.sql.execute_and_fetch_one("mail_templates/get_by_code",{code:code});
		if (!mail) throw "No mail template "+code+" found in database";
		if (!hash.from) hash.from=mail.default_from;
		if (!hash.to) hash.to=mail.default_to.split(/\s*[;,]\s*/);
		
		if (!hash.from) hash.from="no-reply@"+space.site.names[0];
		var headers = {
			From: hash.from,
			"Content-Type": 'text/plain; charset="UTF-8"'
		};
		if (hash.to.constructor!=Array) hash.to=[hash.to];
		//throw space.Dumper({hash:hash,mail:mail,dt:mail.default_to.split(/\s*,\s*/)});
		space.mail=hash;
		var subject=space.views.apply(mail.subject,space);
		var body=space.views.apply(mail.body,space);
		var ret=[];
		for (var i=0;i<hash.to.length;i++) {
			if (hash.to[i].match(/\w@\w/)) require("mail").mail(hash.to[i],subject,body,headers);
			ret.push(hash.to[i]);
		}
		return ret;
	}
};

exports.controllers={
	/*
		Function: controllers.get_option

		Returns:
			An option from space.site[param] or space.system[param]
	*/
	get_option: function(space,param) {
		return space.functions.get_option(space,param.split(/\./));
	},
	/*
		Function: controllers.rewrite_redirect

		Can be used when making rewrites

		Parameters:
			params - (optional) hash of:
			params.url - Location to make a redirect
			rewr - (optional) A page to redirect to
	*/
	rewrite_redirect: function(space,params,rewr) {
		if (!params) params={url:rewr.alias};
//		Status: 302 Found
//		Location: http://somewhere.else/in/movie/land
		space.page.statusCode="302";
		space.page.responseHeaders={"Location":params.url};
		return undefined;
	},
	/*
		Function: controllers.mfact_list

		TODO WHAT IS THAT?

		Parameters:
			params - A hash, containing ModelName and filtering rules.
		
		Returns:
			Returns an array of models
	*/
	mfact_list: function(space,params) {
		var filter={};
		for (var k in params) {
			if (k=="ModelName") continue;
			filter[k]=params[k];
		}
		return space.mfact.list(params.ModelName,filter);
	},

	/*
		Function: controllers.embeded_text

		Parameters:
			code from Tembeded_texts

		Returns:
			body.
	*/
	embeded_text: function(space,params) {
		var et=space.mfact.get("embeded_text",{code:params});
		if (!et) return "<b>[ET "+params+"]</b>";
		return et.body;
	},
	
	/*
		Function: controllers.same_page
		
		Parameters:
			params - a hash with parameter names to skip
			params.add_tail - if ==1, then add ? or & to the end of url

		Returns:
			URL
	*/
	same_page: function(space,params) {
		var r=space.action_src||space.action;
		var i=0;
		for (var k in space.fields) {
			if (params[k]) continue;
			if (k=="foreach_i") continue;
			if (space.std_rewrite_fields && space.std_rewrite_fields[k]) continue;
			r+=i?"&":"?";
			r+=k;
			r+="=";
			r+=space.fields[k];
			i++;
		}
		if (params.add_tail) r+=i?"&":"?";
		return r;
	},
	/*
		Function: controllers.paginator

		Parameters:
			params - a hash of:
			params.template - a template to render, defaults to 'paginator/body'
			params.ap_value - active page
			params.ap_name - (if ap_value is not used) a name of field in space to use for active page
			params.cntp_value - pages count
			params.cntp_name - (if cntp_value is not used) a name of field in space to use for count of pages
			params.cntv_value - values count, divided by page_size if cntp_* are not used
			params.cntv_name - 
			params.page_size - page size, needed when cntv_* used
	*/
	paginator: function(space,params) {
		var ap;
		if (params.ap_value) {
			ap=params.ap_value;
		} else if (params.ap_name) {
			ap=space[params.ap_name];
		} else {
			ap=space.fields.page||0;
		}
		ap=ap*1;
		var cntp;
		if (params.cntp_value) {
			cntp=params.cntp_value;
		} else if (params.cntp_name) {
			cntp=space[params.cntp_name];
		} else if (params.cntv_value) {
			cntp=Math.ceil(params.cntv_value/params.page_size);
		} else if (params.cntv_name) {
			cntp=Math.ceil(space[params.cntv_name]/params.page_size);
		} else {
			throw "Misconfigured paginator()";
		}
		var p=space.paginator=[];
		var baseurl=space.controllers.same_page(space,{page:1,add_tail:1})+"page=";
		function add_pages(start,end) {
			for (var i=start;i<end;i++) {
				p.push({url: baseurl+i,n:i+1,active: ap==i?1:0});
			}
		}
		if (cntp<=10) {
			add_pages(0,cntp);
		} else {
			if (ap<5) {
				add_pages(0,6);p.push({split:1});add_pages(cntp-1,cntp);
			} else if (ap>=cntp-5) {
				add_pages(0,1);p.push({split:1});add_pages(cntp-6,cntp);
			} else {
				add_pages(0,1);p.push({split:1});add_pages(ap-3,ap+4);p.push({split:1});add_pages(cntp-1,cntp);
			}
		}
		if (ap!=0) space.prev_page_url=baseurl+(ap-1);
		if (ap<cntp-1) space.next_page_url=baseurl+(ap+1);
		return space.views.process("widgets/"+(params.template||"paginator/body"),space);
	},

	/*

		Parameters:
			params - a hash of
			params.model_name
			params.filter
			params.page_size
			params.paginator_template - (can be empty)

		Returns:
			a hash of:
				return.fetched_models - array of models
				return.paginator_result - an HTML part of paginator
				return.prev_page_url
				return.next_page_url
	*/
	paginator_model: function(space,params) {
		if (!params.filter) params.filter={};
		var cnt=space.mfact.count(params.model_name,params.filter);
		params.filter.limit=params.page_size;
		params.filter.offset=params.page_size*(space.fields.page||0);
		var part=space.models=space.mfact.list(params.model_name,params.filter);

		var paginator_result=space.controllers.paginator(space,{
			template: params.paginator_template,
			cntv_value: cnt,
			page_size: params.page_size
		});
		var baseurl=space.controllers.same_page(space,{page:1,add_tail:1})+"page=";
		var ret={
			paginator_result: paginator_result,
			fetched_models: part
		};
		var ap=1*(space.fields.page||0)
		if (ap!=0) ret.prev_page_url=baseurl+(ap-1);
		if (ap<Math.ceil(cnt/params.page_size)-1) ret.next_page_url=baseurl+(ap+1);
		return ret;
	},

	/*
		Parameters:
			params.template
			PLUS all params of paginator_model

		Returns:
			A template, rendered with extra space values taken from hash of paginator_model
	*/
	paginator_model_html: function(space,params) {
		var a=space.controllers.paginator_model(space,params);
		for (var k in a) space[k]=a[k];
		return space.views.process("widgets/"+params.template,space);
	},

	mobile_map_site: function(space,options) {
		var protocol="http://";
		if (options && options.protocol) protocol=options.protocol;
		if (system.env.HTTP_USER_AGENT.match(/iphone|ipad/i)) return protocol+"maps.apple.com/maps?q=";
		return protocol+"maps.google.com/maps?q=";
	}
};
/*****************************************************************************/

